#include <IDK.hpp>

#include <Resources.hpp>
#include <Mesh.hpp>

namespace Core
{
	//
	// TMeshLOD
	//

	//-------------------------------------------------------
	// TMeshLOD
	//-------------------------------------------------------
	TMeshLOD::TMeshLOD(Threading::IThreadEvent& signal, ref<IDK::IStream> stream) : Base(signal, stream, IDK::IResource::NormalPriority-2)
	{
		mCount[Type::Trinagle] = 0;
		mCount[Type::Vertex] = 0;
	}

	//-------------------------------------------------------
	// Cleanup
	//-------------------------------------------------------
	void TMeshLOD::Cleanup(void)
	{
		if(mTrinagleData != NullPtr)
		{
			iAssert(mTrinagleData->IsUnique(), "Trinagle data is used other smart ptr's");
			mTrinagleData = NullPtr;
		}

		if(mVertexData != NullPtr)
		{
			iAssert(mVertexData->IsUnique(), "Vertex data is used other smart ptr's");
			mVertexData = NullPtr;
		}

		Base::Cleanup();
	}

	//-------------------------------------------------------
	// OnChangeLoadStatus
	//-------------------------------------------------------
	void TMeshLOD::OnChangeLoadStatus(LoadStatus::Enum status)
	{
		Base::OnChangeLoadStatus(status);
	}

	//-------------------------------------------------------
	// BindToGPU
	//-------------------------------------------------------
	void TMeshLOD::BindToGPU(IDK::IRenderState& rndState)
	{
		if(mIndexBuffer == NullPtr || mIndexBuffer->IsInvalid())
		{
			mIndexBuffer = rndState.CreateIndexBuffer(sizeof(TrinagleAttrib), mCount[Type::Trinagle], mTrinagleData.As<HandlePtr::Instance>());
		}

		if(mVertexBuffer == NullPtr || mVertexBuffer->IsInvalid())
		{
			mVertexBuffer = rndState.CreateVertexBuffer(sizeof(VertexAttrib), mCount[Type::Vertex], mVertexData.As<HandlePtr::Instance>());
		}
	}

	//-------------------------------------------------------
	// Render
	//-------------------------------------------------------
	bool TMeshLOD::Render(IDK::IRenderState& rndState)
	{
		BindToGPU(rndState);

		rndState.SetVertexBuffer(mVertexBuffer);
		rndState.SetIndexBuffer(mIndexBuffer);

		rndState.DrawIndexVertexData(IDK::IRenderState::PrimitiveType::TrinagleList, mCount[Type::Trinagle]);
		
		rndState.SetVertexBuffer(NullPtr);
		rndState.SetIndexBuffer(NullPtr);

		return true;
	}

	//-------------------------------------------------------
	// Rendered
	//-------------------------------------------------------
	void TMeshLOD::Rendered(IDK::IRenderState& /* rndState*/)
	{
	}

	//-------------------------------------------------------
	// OnTickLoad
	//-------------------------------------------------------
	bool TMeshLOD::OnTickLoad(void)
	{
		ref<IDK::IStream> reader = GetResourceStream();

		reader->Open(); //open res stream
		iLog("Mesh to load:");
		iLog(reader->GetPath()->Chars());

		//read version :) maybe using future
		reader->Read<uint16>();
		String name = reader->ReadString();
		iLog(name->Chars());

		mCount[Type::Trinagle] = reader->Read<uint16>();
		mTrinagleData = TrinagleData(new TrinagleData::Create(mCount[Type::Trinagle]));

		{ // Read Trinagles
			const uint MaxSizeInBytes = mCount[Type::Trinagle] * sizeof(TrinagleAttrib);
			reader->ReadData(mTrinagleData, MaxSizeInBytes);
		}

		mCount[Type::Vertex] = reader->Read<uint16>();
		mVertexData = VertexData(new VertexData::Create(mCount[Type::Vertex]));

		{// Read Vertex'es
			const uint MaxSizeInBytes = mCount[Type::Vertex] * sizeof(VertexAttrib);
			reader->ReadData(mVertexData, MaxSizeInBytes);
		}

		reader->Close();
		return true;
	}

	//
	// TMeshLODStreaming
	//
	TMeshLODStreaming::TMeshLODStreaming(Threading::IThreadEvent& signal, ref<IDK::IStream> stream)
		: TMeshLOD(signal, stream), mLoadProcess(LoadProcess::None), mIndecesReadInBytes(0), mVerticesReadInBytes(0)
	{
	}

	//-------------------------------------------------------
	// Cleanup
	//-------------------------------------------------------
	void TMeshLODStreaming::Cleanup(void)
	{
		//Reset streaming helpers flags
		mLoadProcess = LoadProcess::None;

		Base::Cleanup();
	}

	//-------------------------------------------------------
	// OnTickLoad
	//-------------------------------------------------------
	bool TMeshLODStreaming::OnTickLoad(void)
	{
		ref<IDK::IStream> reader = GetResourceStream();
		switch(mLoadProcess)
		{
		case LoadProcess::None:
			reader->Open(); //open res stream
			//iLog("Mesh to load:");
			//iLog(reader->GetPath()->Chars());
			mLoadProcess = LoadProcess::VersionAndName; //next
			break;

		case LoadProcess::VersionAndName:
			//read version :) maybe using future
			reader->Read<uint8>();
			reader->ReadString();
			mLoadProcess = LoadProcess::InitIndeces; //next
			break;

		case LoadProcess::InitIndeces:
			mCount[Type::Trinagle] = reader->Read<uint16>();
			mTrinagleData = TrinagleData(new TrinagleData::Create(mCount[Type::Trinagle]));
			mIndecesReadInBytes = 0;
			mLoadProcess = LoadProcess::ReadIndeces; //next
			break;

		case LoadProcess::ReadIndeces:
			{
				const uint MaxSizeInBytes = mCount[Type::Trinagle] * sizeof(TrinagleAttrib);
				const uint NeedReadInBytes = 65535; //max read bytes for one iteration
				const uint PosRead = MaxSizeInBytes - mIndecesReadInBytes;

				if(PosRead > NeedReadInBytes)
				{
					reader->ReadData(mTrinagleData, NeedReadInBytes, mIndecesReadInBytes);
					mIndecesReadInBytes += NeedReadInBytes;
				}
				else
				{
					reader->ReadData(mTrinagleData, PosRead, mIndecesReadInBytes);
					mIndecesReadInBytes = 0;
					mLoadProcess = LoadProcess::InitVertices; //next
				}
			}
			break;

		case LoadProcess::InitVertices:
			mCount[Type::Vertex] = reader->Read<uint16>();
			mVertexData = VertexData(new VertexData::Create(mCount[Type::Vertex]));
			mVerticesReadInBytes = 0;
			mLoadProcess = LoadProcess::ReadVertices; //next
			break;

		case LoadProcess::ReadVertices:
			{
				const uint MaxSizeInBytes = mCount[Type::Vertex] * sizeof(VertexAttrib);
				const uint NeedReadInBytes = 65535; //max read bytes for one iteration
				const uint PosRead = MaxSizeInBytes - mVerticesReadInBytes;
				if(PosRead > NeedReadInBytes)
				{
					reader->ReadData(mVertexData, NeedReadInBytes, mVerticesReadInBytes);
					mVerticesReadInBytes += NeedReadInBytes;
				}
				else
				{
					reader->ReadData(mVertexData, PosRead, mVerticesReadInBytes);
					mVerticesReadInBytes = 0;
					mLoadProcess = LoadProcess::Complete; //next
				}
			}
			break;

		case LoadProcess::Complete:
			reader->Close();
			mLoadProcess = LoadProcess::None;
			return true;
		}
		return false;
	}

	//
	// TMesh
	//

	//-------------------------------------------------------
	// TMesh
	//-------------------------------------------------------
	TMesh::TMesh(const String &path) : Base(), mPath(path)
	{
	}

	//-------------------------------------------------------
	// GetPath
	//-------------------------------------------------------
	String TMesh::GetPath(void) const
	{
		return mPath;
	}

	//-------------------------------------------------------
	// AddLOD
	//-------------------------------------------------------
	void TMesh::AddLOD(MeshLOD meshLOD)
	{
		Add(meshLOD);
	}

	//-------------------------------------------------------
	// Rendered
	//-------------------------------------------------------
	void TMesh::Rendered(IDK::IRenderState&/* rndState*/)
	{
		//not impl
	}

	//-------------------------------------------------------
	// Render
	//-------------------------------------------------------
	bool TMesh::Render(IDK::IRenderState& rndState)
	{
		//Calculate distance to camera using rndState->GetOcclusionQuery()->GetNearDistance()
		MeshLOD drawElement = Get(0);

		switch(drawElement->GetLoadStatus())
		{				
		case IDK::IResource::LoadStatus::Wait:
			drawElement->SetLoadStatus(IDK::IResource::LoadStatus::Loading);
			break;

		case IDK::IResource::LoadStatus::Loaded:
			drawElement->Render(rndState); // bind to gpu
			drawElement->SetLoadStatus(IDK::IResource::LoadStatus::Ready);
			drawElement->Cleanup();
			break;
		case IDK::IResource::LoadStatus::Ready:
			//Drawing
			drawElement->Render(rndState);
			break;
		}
		return true;
	}
}
